package filter;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 *
 * @author dakhoa
 * @version 1.0
 */
public class ConversationManager {

    public static final String FLOW_ID = "_flowId";
    public static final String FLOW_END_ID = "_flowEnd";
    private static final String CONVERSATION_MANAGER_INSTANCE = ConversationManager.class.getName();
    private static final String FLOW_SCOPE = "flowScope";
    private Map<String, Map<String, Object>> flowScopeMap;
    private String currentConversationId;
    private boolean isFlowEndSignal;
    private int numberOfFlow;
    private HttpSession session;
    private static ConversationManager instance;

    private ConversationManager() {
    }

    public static ConversationManager getInstance() {
        return instance;
    }

    /**
     * <PRE>
     * Since the returning flowScope was implemented by  Collections.synchronizedMap
     * to ensure thread-safe. So copie this advice from Collections.synchronizedMap  method:
     *
     *
     * It is imperative that the user manually synchronize on the returned map when iterating
     * over any of its collection views:
     *   Map m = Collections.synchronizedMap(new HashMap());
     *   ...
     *   Set s = m.keySet();  // Needn't be in synchronized block
     *   ...
     *   synchronized(m) {  // Synchronizing on m, not s!
     *       Iterator i = s.iterator(); // Must be in synchronized block
     *       while (i.hasNext())
     *       foo(i.next());
     *  }
     *
     *   Failure to follow this advice may result in non-deterministic behavior.
     *</PRE>
     * @return
     */
    public Map<String, Object> getFlowScope() {
        return flowScopeMap.get(currentConversationId);
    }

    public Map<String, Object> getFlowScope(String id) {
        return flowScopeMap.get(id);
    }

    /**
     * 
     * @param created value to true to force create new flowScope but not yet activated in
     * session (mean that this new flowScope cannot be el access in jsp). This
     * specially for open dialog usage who need to pass data to dialog before
     * open it. Value to false behave like no arg version
     * 
     * @return
     */
    public Map<String, Object> getFlowScope(boolean created) {
        if (created) {
            String newConversationId = String.valueOf(++numberOfFlow);
            startNewConversation(newConversationId, false);
            return flowScopeMap.get(newConversationId);
        }

        return flowScopeMap.get(currentConversationId);
    }

    /**
     * Normally the flowScope will be automatic dispose when conversation ending.
     * This implementation specially for dialog who might need to get data from
     * dialog before dispose it's flowScope manually
     *
     * @param flScope
     */
    public void disposeFlowScope(Map<String, Object> flScope) {
        if (flScope != null) {
            String flId = (String) flScope.get(FLOW_ID);
            synchronized (flowScopeMap) {
                flowScopeMap.remove(flId);
            }
            flScope.clear();
        }
    }

    public String getConversationId() {
        return currentConversationId;
    }

    protected static synchronized ConversationManager currentInstance(HttpServletRequest request) {
        HttpSession session = request.getSession();

        ConversationManager sessionInstance = (ConversationManager) session.getAttribute(CONVERSATION_MANAGER_INSTANCE);

        if (sessionInstance == null) {
            sessionInstance = new ConversationManager();
            sessionInstance.flowScopeMap = new HashMap<String, Map<String, Object>>();
            sessionInstance.session = session;
            session.setAttribute(CONVERSATION_MANAGER_INSTANCE, sessionInstance);
        }

        instance = sessionInstance;
        instance.handleRequest(request);

        return instance;
    }

    protected boolean isSignalEndFlow() {
        return isFlowEndSignal;
    }

    private void handleRequest(HttpServletRequest request) {
        isFlowEndSignal = "t".equalsIgnoreCase(request.getParameter(FLOW_END_ID));
        currentConversationId = request.getParameter(FLOW_ID);

        if (isFlowEndSignal) {
            disposeFlowScope(flowScopeMap.get(currentConversationId));
            return;
        }

        if (currentConversationId == null) {
            startNewConversation();

        } else {
            activateConversation(currentConversationId);
        }
    }

    private void startNewConversation() {
        currentConversationId = String.valueOf(++numberOfFlow);
        startNewConversation(currentConversationId, true);
    }

    private void activateConversation(String conversationId) {
        Map fs = flowScopeMap.get(conversationId);
        if (fs != null) {
            session.setAttribute(FLOW_SCOPE, getFlowScope());

        } else {
            startNewConversation(conversationId, true);
        }
    }

    private void startNewConversation(String conversationId, boolean activate) {
        Map<String, Object> flScope = Collections.synchronizedMap(new HashMap<String, Object>());
        flScope.put(FLOW_ID, conversationId);
        flowScopeMap.put(conversationId, flScope);
        if (activate) {
            activateConversation(conversationId);
        }
    }
}
